package com.showcase.cloud.gateway.feign.config;


import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.sun.showcase.exception.DefaultException;
import com.sun.showcase.pojo.Result;
import feign.FeignException;
import feign.Response;
import feign.Types;
import feign.Util;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringDecoder;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.*;
import java.util.Arrays;


/**
 * 针对Result对象进行处理
 * Feign调用返回后对返回内容进行解析，对Result对象自动解包
 *
 * @author j
 */
public class RFeignDecoder extends SpringDecoder {

    public RFeignDecoder(ObjectFactory<HttpMessageConverters> messageConverters) {
        super(messageConverters);
    }

    @Override
    public Object decode(final Response response, Type type) throws IOException, FeignException {
		Class clazz = Types.getRawType(type);
		if (clazz != Result.class) {
			Type rType = newParameterizedTypeImpl(Result.class, type);
			Object obj = null;
			String content = Util.toString(response.body().asReader());
			Response newResponse = response.toBuilder().body(content, Util.UTF_8).build();
			try {
				obj = super.decode(newResponse, rType);
			}catch (Exception e){
				if(JSONUtil.isJson(content)){
					JSONObject jsonObject = JSONUtil.parseObj(content);
					obj = jsonObject.toBean(Result.class,true);
				}
			}
			if (obj != null) {
                Result r = (Result) obj;
				if (r.isSuccess()) {
					return r.getData();
				} else {
					throw new DefaultException(r.getMsg(),Integer.valueOf(r.getCode()));
				}
			}
			throw new DefaultException(content,0);

		}else {
			return super.decode(response, type);
		}
    }

    private static ParameterizedTypeImpl newParameterizedTypeImpl(Class<?> clazz, Type... types) {
        return new ParameterizedTypeImpl(clazz.getEnclosingClass(), clazz, types);
    }


    private static void checkNotPrimitive(Type type) {
        if (type instanceof Class<?> && ((Class<?>) type).isPrimitive()) {
            throw new IllegalArgumentException();
        }
    }

    private static int hashCodeOrZero(Object o) {
        return o != null ? o.hashCode() : 0;
    }

    private static String typeToString(Type type) {
        return type instanceof Class ? ((Class<?>) type).getName() : type.toString();
    }


    private static boolean equal(Object a, Object b) {
        return a == b || (a != null && a.equals(b));
    }

    /**
     * Returns true if {@code a} and {@code b} are equal.
     */
    static boolean equals(Type a, Type b) {
        if (a == b) {
            // Also handles (a == null && b == null).
            return true;

        } else if (a instanceof Class) {
            // Class already specifies equals().
            return a.equals(b);

        } else if (a instanceof ParameterizedType) {
            if (!(b instanceof ParameterizedType)) {
                return false;
            }
            ParameterizedType pa = (ParameterizedType) a;
            ParameterizedType pb = (ParameterizedType) b;
            return equal(pa.getOwnerType(), pb.getOwnerType())
                    && pa.getRawType().equals(pb.getRawType())
                    && Arrays.equals(pa.getActualTypeArguments(), pb.getActualTypeArguments());

        } else if (a instanceof GenericArrayType) {
            if (!(b instanceof GenericArrayType)) {
                return false;
            }
            GenericArrayType ga = (GenericArrayType) a;
            GenericArrayType gb = (GenericArrayType) b;
            return equals(ga.getGenericComponentType(), gb.getGenericComponentType());

        } else if (a instanceof WildcardType) {
            if (!(b instanceof WildcardType)) {
                return false;
            }
            WildcardType wa = (WildcardType) a;
            WildcardType wb = (WildcardType) b;
            return Arrays.equals(wa.getUpperBounds(), wb.getUpperBounds())
                    && Arrays.equals(wa.getLowerBounds(), wb.getLowerBounds());

        } else if (a instanceof TypeVariable) {
            if (!(b instanceof TypeVariable)) {
                return false;
            }
            TypeVariable<?> va = (TypeVariable<?>) a;
            TypeVariable<?> vb = (TypeVariable<?>) b;
            return va.getGenericDeclaration() == vb.getGenericDeclaration()
                    && va.getName().equals(vb.getName());

        } else {
            // This isn't a type we support!
            return false;
        }
    }

    static final class ParameterizedTypeImpl implements ParameterizedType, Serializable {

        private final Type ownerType;
        private final Type rawType;
        private final Type[] typeArguments;

        ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) {
            // Require an owner type if the raw type needs it.
            if (rawType instanceof Class<?>
                    && (ownerType == null) != (((Class<?>) rawType).getEnclosingClass() == null)) {
                throw new IllegalArgumentException();
            }

            this.ownerType = ownerType;
            this.rawType = rawType;
            this.typeArguments = typeArguments.clone();

            for (Type typeArgument : this.typeArguments) {
                if (typeArgument == null) {
                    throw new NullPointerException();
                }
                checkNotPrimitive(typeArgument);
            }
        }

        @Override
        public Type[] getActualTypeArguments() {
            return typeArguments.clone();
        }

        @Override
        public Type getRawType() {
            return rawType;
        }

        @Override
        public Type getOwnerType() {
            return ownerType;
        }

        @Override
        public boolean equals(Object other) {
            return other instanceof ParameterizedType && RFeignDecoder.equals(this, (ParameterizedType) other);
        }

        @Override
        public int hashCode() {
            return Arrays.hashCode(typeArguments) ^ rawType.hashCode() ^ hashCodeOrZero(ownerType);
        }

        @Override
        public String toString() {
            StringBuilder result = new StringBuilder(30 * (typeArguments.length + 1));
            result.append(typeToString(rawType));
            if (typeArguments.length == 0) {
                return result.toString();
            }
            result.append("<").append(typeToString(typeArguments[0]));
            for (int i = 1; i < typeArguments.length; i++) {
                result.append(", ").append(typeToString(typeArguments[i]));
            }
            return result.append(">").toString();
        }


    }


}
